<template>
  <div
    ref="container"
    class="column-stat-component"
    :style="{ width: `${width}px`, height: `${height}px` }"
    @mousedown="onContainerMousedown($event)"
    @mouseup="onContainerMouseup"
    @click.stop
  >
    <div
      v-if="mode === 'discrete'"
      class="discrete"
      @contextmenu="onContextMenu($event)"
    >
      <div
        ref="detailBox"
        class="detail-box"
        @mousewheel="onDetailScroll($event)"
        @mouseleave="onMouseLeave"
        @mousemove="onMouseMove($event)"
      >
        <div
          v-for="item in selfValue"
          :key="item.label"
          class="detail-outside"
          :style="{ marginBottom: `${barGap}px` }"
          @mouseenter="onMouseEnterBar($event, item)"
          @click.stop="onBarClick($event, item)"
        >
          <div
            :class="{ 'detail-basis': true }"
            :style="{ width: calcBarStyle(item.value[0]) }"
          ></div>
          <div
            v-show="item.value[1]"
            class="detail-highlight"
            :style="{ width: calcBarStyle(item.value[1], 'highlight') }"
          ></div>
          <!-- 异常值 anomaly -->
          <div
            v-show="item.value[2]"
            class="detail-anomaly"
            :style="{ width: calcBarStyle(item.value[2], 'anomaly') }"
          ></div>
          <!-- imputation -->
          <div
            v-show="item.value[3]"
            class="detail-imputation"
            :style="{ width: calcBarStyle(item.value[3], 'imputation') }"
          ></div>
          <label
            class="discrete-label label"
            :class="{
              'is-matching-flag':
                urbanSemanticList.includes(semantic) &&
                item.isMatching === false,
            }"
            >{{ `${item.label}` }}</label
          >
        </div>
      </div>
      <div
        v-show="
          selfValue.length >= discreteVisualBarNumber &&
          selfOverviewOptions.length > 0
        "
        ref="rangeBox"
        class="range-box"
        @mousewheel="onRangeScroll($event)"
        @mousemove="onOverviewMouseMove($event)"
      >
        <div
          v-show="selfValue.length >= discreteVisualBarNumber"
          class="range-overview"
          :style="{
            height: `${rangeOverviewHeight}px`,
            top: `${rangeOverviewTop}px`,
          }"
          @mousedown="onMouseDown($event)"
        ></div>
        <div
          v-for="(item, index) in selfOverviewOptions"
          :key="`range-${index}`"
          class="range-item"
          @click.stop="onRangeBarClick($event, item, index)"
        >
          <div
            class="range-basis"
            :style="{ width: calcBarStyle(item[0], 'basis', rangeMaxValue) }"
          ></div>
          <div
            v-show="item[1]"
            class="range-highlight"
            :style="{
              width: calcBarStyle(item[1], 'highlight', rangeMaxValue),
            }"
          ></div>
          <!-- 异常值 anomaly -->
          <div
            v-show="item[2]"
            class="range-anomaly"
            :style="{ width: calcBarStyle(item[2], 'anomaly', rangeMaxValue) }"
          ></div>
          <!-- imputation -->
          <div
            v-show="item[3]"
            class="range-imputation"
            :style="{
              width: calcBarStyle(item[3], 'imputation', rangeMaxValue),
            }"
          ></div>
        </div>
      </div>
    </div>

    <div
      v-if="mode === 'continuous'"
      ref="continuous"
      class="continuous"
      @contextmenu="onContextMenu($event)"
    >
      <div class="axis-label" :style="{ width: calcLabelWidth }">
        <div
          v-for="item in tickPoints"
          :key="`label-${item.label}`"
          class="label"
          :title="item.label"
          :style="item.labelStyle"
        >
          {{ item.label }}
        </div>
      </div>
      <div class="axis" :style="{ height: `${axisHeight}px` }">
        <div
          v-for="item in tickPoints"
          :key="`axis-${item.label}`"
          class="axis-tick"
          :style="item.tickStyle"
        ></div>
      </div>
      <div
        ref="continuousDetailBox"
        class="detail-box"
        @mouseleave="onMouseLeave"
        @mousemove="onMouseMove($event)"
      >
        <div
          v-for="item in selfValue"
          :key="item.label"
          class="detail-outside"
          @mouseenter="onMouseEnterBar($event, item)"
          @click.stop="onBarClick($event, item)"
        >
          <div
            :class="{ 'detail-basis': true }"
            :style="{ width: calcBarStyle(item.value[0]) }"
          ></div>
          <div
            v-show="item.value[1]"
            class="detail-highlight"
            :style="{ width: calcBarStyle(item.value[1], 'highlight') }"
          ></div>
          <!-- 异常值 anomaly -->
          <div
            v-show="item.value[2]"
            class="detail-anomaly"
            :style="{ width: calcBarStyle(item.value[2], 'anomaly') }"
          ></div>
          <!-- imputation -->
          <div
            v-show="item.value[3]"
            class="detail-imputation"
            :style="{ width: calcBarStyle(item.value[3], 'imputation') }"
          ></div>
        </div>
      </div>
    </div>

    <column-stat-tooltip
      :visible="tooltipVisible"
      :tooltip-style="tooltipStyle"
    >
      <template #title>
        {{ tooltipData.label }}
      </template>
      <template #content>
        <div class="tooltip-value">
          {{ tooltipData.value }}行（{{ tooltipData.percent }}）
        </div>
        <div v-show="tooltipData.highlightValue" class="highlight">
          {{ tooltipData.highlightValue }}（{{
            tooltipData.highlightPercent
          }}）已突出显示
        </div>
        <!-- 异常 -->
        <div v-show="tooltipData.anomalyValue" class="highlight">
          {{ tooltipData.anomalyValue }}（{{
            tooltipData.anomalyPercent
          }}）已突出显示
        </div>
        <!-- 插补 -->
        <div v-show="tooltipData.imputationValue" class="highlight">
          {{ tooltipData.imputationValue }}（{{
            tooltipData.imputationPercent
          }}）已突出显示
        </div>
      </template>
    </column-stat-tooltip>

    <div class="selection-rect" :style="rectStyle"></div>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
import ColumnStatTooltip from '@/components/studio/data/node-edit-panel/ColumnStatTooltip.vue'

interface ITickPoint {
  label: string
  tickStyle?: {
    top: string
  }
  labelStyle?: {
    top: string
  }
}

interface ITooltipData {
  label: string
  value: number
  percent: string
  highlightValue?: number
  highlightPercent?: string
  anomalyValue?: number
  anomalyPercent?: string
  imputationValue?: number
  imputationPercent?: string
}

let lastDetailScrollTop = 0

@Component({
  components: {
    ColumnStatTooltip,
  },
})
export default class ColumnStatistics extends Vue {
  @Prop({ default: 'discrete' }) mode!: 'discrete' | 'continuous'
  @Prop() width!: number
  @Prop() height!: number // 结合rangeMaxCount来计算range高度
  @Prop({ default: 60 }) rangeMaxCount!: number
  @Prop() barHeight!: number
  @Prop({ default: 0 }) barGap!: number
  @Prop({ default: 1 }) curPage!: number // discrete模式下当前页码
  @Prop({ default: 50 }) pageSize!: number // discrete模式下每页条数
  @Prop({ default: 18015 }) totalValue!: number // 总条数，discrete模式下算和range间的比例
  @Prop({ default: 17766 }) maxValue!: number // 数据中最大的值，用于计算bar的百分比长度
  @Prop({ default: 1 }) totalCount!: number // 总条数，用于计算tooltip中的百分比
  @Prop({ default: () => [] }) value!: any[]
  @Prop({ default: () => [] }) overviewOptions!: any
  @Prop({ default: false }) contextmenuEnable!: boolean // 是否启用右键事件
  @Prop({ default: null }) semantic!: any //  字段语义

  private tickPoints: ITickPoint[] = []
  private gap: number = 0
  private gapCount: number = 4 // 连续模式下每组个数
  private axisHeight: number = 0
  private tooltipVisible: boolean = false
  private tooltipData: ITooltipData | {} = {}
  private tooltipStyle: any = {}
  private tooltipTarget: any = null
  private rangeTotalRatio: number = Math.ceil(
    this.totalValue / this.overviewOptions.length
  )
  private rangeOverviewHeight: number = 5 // overview中可视区域框的最小高度
  private rangeOverviewTop: number = 0 //  overview中可视区域框的top位置
  private rangeBarHeight: number = 2 // overview内容区bar的高度
  private rangeBarGap: number = 1 // overview内容区bar的间隔
  private detailBoxLastScrollTop: number = 0
  private selfValue: any[] = this.$props.value || []
  private selfOverviewOptions: any[] = this.$props.overviewOptions
  private discreteVisualBarNumber: number = 7
  private isDragOverview: boolean = false
  private overviewOldTop: number = 0
  private isSelection: boolean = false // 是否开始框选
  private selectionRect: {
    left: number
    top: number
    width: number
    height: number
  } | null = null
  private selectionBarData: any[] = [] // 框选或者多选选中的bar数据
  public urbanSemanticList: string[] = ['country', 'province', 'city'] //  地图语义列表，仅为地图语义才有是否匹配标识和清洗推荐

  /**
   * 获取右侧overview中数据最大的值，用于计算overview中bar的宽度
   */
  private get rangeMaxValue() {
    let value = 0
    this.selfOverviewOptions.forEach((item: number[]) => {
      if (item[0] > value) {
        ;[value] = item
      }
    })
    return value
  }

  /**
   * 获取选框的位置和大小样式
   */
  private get rectStyle() {
    if (!this.selectionRect) {
      return { top: '0px', left: '0px', width: '0px', height: '0px' }
    }
    const { top, left } = this.$el.getBoundingClientRect()
    const {
      top: selectionTop,
      left: selectionLeft,
      width: sectionWidth,
      height: sectionHeight,
    } = this.selectionRect
    return {
      left: `${selectionLeft - left + (sectionWidth < 0 ? sectionWidth : 0)}px`,
      top: `${selectionTop - top + (sectionHeight < 0 ? sectionHeight : 0)}px`,
      width: `${Math.abs(sectionWidth)}px`,
      height: `${Math.abs(sectionHeight)}px`,
    }
  }

  mounted() {
    console.log(this.selfValue)
    if (this.mode === 'continuous') {
      this.initContinuousConfig()
    } else {
      this.initDiscreteConfig()
    }
    // document.addEventListener('click', this.onDocumentClick)
  }

  beforeDestroy() {
    // document.removeEventListener('click', this.onDocumentClick)
  }

  @Watch('value')
  onValueChange(newValue: any[]) {
    this.selfValue = newValue
    this.$nextTick(() => {
      if (this.selfValue.length === 0) {
        return
      }
      if (this.mode === 'continuous') {
        this.initContinuousConfig()
      } else {
        this.initDiscreteConfig()
      }
    })
  }

  @Watch('overviewOptions')
  onOverviewOptionsChange(newValue: any[]) {
    this.selfOverviewOptions = newValue
    this.rangeTotalRatio = Math.ceil(this.totalValue / newValue.length)
    this.rangeOverviewTop = 0
    if (this.$refs.detailBox) {
      ;(this.$refs.detailBox as Element).scrollTop = 0
    }
  }

  // 初始化连续模式的相关配置
  private initContinuousConfig() {
    this.initAxisHeight()
    this.initAxisTickPoints()
  }

  // 初始化详细模式的相关配置
  private initDiscreteConfig() {
    this.initRangeView()
  }

  private initAxisHeight() {
    if (this.mode !== 'continuous') {
      return
    }
    const { length } = this.selfValue
    this.gap = Math.ceil(length / this.gapCount)

    this.axisHeight = length * (this.barHeight + this.barGap)
  }

  /**
   * 初始化标签刻度
   */
  private initAxisTickPoints() {
    const points: ITickPoint[] = [
      {
        label:
          this.selfValue[0].low === null ? 'null' : `${this.selfValue[0].low}`,
        tickStyle: {
          top: '0px',
        },
        labelStyle: {
          top: '-8px',
        },
      },
    ]
    /* eslint-disable no-plusplus */
    for (let i = 1; i < this.gap; i++) {
      const top = (this.barHeight + this.barGap) * i * this.gapCount
      points.push({
        label: `${this.selfValue[i * 4].low as number}`,
        tickStyle: {
          top: `${top}px`, //
        },
        labelStyle: {
          top: `${top - 8}px`, // 8px 是label和标点在垂直方向上的差异
        },
      })
    }
    this.tickPoints = points
  }

  /**
   * 初始化range相关的展示
   */
  private initRangeView() {
    if (!this.$refs.detailBox) {
      return
    }
    const detailBoxHeight = (this.$refs
      .detailBox as Element).getBoundingClientRect().height
    const visualBarNumber = detailBoxHeight / (this.barHeight + this.barGap) // 可视区域的bar条数
    const visualBarRangeHeight =
      (visualBarNumber / this.rangeTotalRatio) *
      (this.rangeBarHeight + this.rangeBarGap) // 可视区域bar条数在overview中的高度
    if (visualBarRangeHeight > 5) {
      // overview中可视区域框默认最小为5
      this.rangeOverviewHeight = visualBarRangeHeight
    } else {
      this.rangeOverviewHeight = 5
    }
  }

  /**
   * 计算连续模式下左侧标签的最大宽度
   */
  private get calcLabelWidth() {
    const span = document.createElement('span')
    span.style.fontSize = '12px'
    span.style.opacity = '0'
    span.className = 'calc-label'
    document.body.append(span)
    let { width } = span.getBoundingClientRect()

    this.selfValue.forEach((item) => {
      span.textContent = `${item.low}`
      if (span.getBoundingClientRect().width > width) {
        width = span.getBoundingClientRect().width
      }
      span.textContent = `${item.up}`
      if (span.getBoundingClientRect().width > width) {
        width = span.getBoundingClientRect().width
      }
    })
    width += 3
    const maxWidth = this.width / 2 // 最大不超过宽度的一半
    span.remove()
    return `${width > maxWidth ? maxWidth : width}px`
  }

  /**
   * 计算展示的百分比
   */
  private calcBarStyle(
    value: number,
    type?: 'basis' | 'highlight' | 'anomaly' | 'imputation',
    maxValue?: number
  ) {
    let percent = ''
    if (typeof value !== 'number' && !value) {
      percent = '0%'
    }
    const max = maxValue || this.maxValue
    let result = (value * 100) / max
    if (result < 1 && result !== 0) {
      result = 2
    }
    percent = `${result}%`
    return percent
  }

  private onMouseMove({ pageX, pageY }: MouseEvent) {
    if (this.isSelection && this.selectionRect) {
      // 如果是框选模式计算拖动距离
      this.selectionRect = {
        ...this.selectionRect,
        width: pageX - this.selectionRect.left,
        height: pageY - this.selectionRect.top,
      }
    } else {
      this.tooltipStyle = {
        top: `${pageY + 15}px`,
        left: `${pageX + 15}px`,
      }
    }
  }

  private onMouseLeave() {
    this.tooltipVisible = false
    this.tooltipTarget = null
  }

  private onMouseEnterBar(event: MouseEvent, barData: any) {
    if (this.isSelection) {
      return
    }
    const [value, highlightValue, anomalyValue, imputationValue] = barData.value
    const data: any = {
      label: `${barData.label}`,
    }
    if (value === 0) {
      data.value = 0
      data.percent = '0%'
    } else {
      const percent = Math.floor((100 * value) / this.totalCount)
      data.value = value
      data.percent = `${percent < 1 ? '<1' : percent}%`
    }
    if (highlightValue === 0) {
      data.highlightValue = 0
      data.highlightPercent = '0%'
    } else {
      const percent = Math.floor((100 * highlightValue) / value)
      data.highlightValue = highlightValue
      data.highlightPercent = `${percent < 1 ? '<1' : percent}%`
    }

    if (anomalyValue === 0) {
      data.anomalyValue = 0
      data.anomalyPercent = '0%'
    } else {
      const percent = Math.floor((100 * anomalyValue) / value)
      data.anomalyValue = anomalyValue
      data.anomalyPercent = `${percent < 1 ? '<1' : percent}%`
    }
    if (imputationValue === 0) {
      data.imputationValue = 0
      data.imputationPercent = '0%'
    } else {
      const percent = Math.floor((100 * imputationValue) / value)
      data.imputationValue = imputationValue
      data.imputationPercent = `${percent < 1 ? '<1' : percent}%`
    }

    this.tooltipData = data
    this.tooltipVisible = true
  }

  private onBarClick(event: MouseEvent, barData: any) {
    event.stopPropagation()
    this.selectionBarData.push(barData)
    this.$emit('bar-click', { selectedDataArray: [barData] }, event)
  }

  /**
   * 点击overview中的bar
   */
  private onRangeBarClick(
    event: MouseEvent,
    rangeBarData: any,
    rangeIndex: number
  ) {
    event.stopPropagation()
    let top = rangeIndex * (this.rangeBarHeight + this.rangeBarGap)
    const rangeContentHeight =
      (this.rangeBarHeight + this.rangeBarGap) * this.selfOverviewOptions.length
    if (top + this.rangeOverviewHeight > rangeContentHeight) {
      top = rangeContentHeight - this.rangeOverviewHeight
    }

    this.rangeOverviewTop = top
    this.$emit('range-bar-click', {
      selectedRangeIndex: rangeIndex,
      rangeBarData,
      rangeIndex,
    })
  }

  private onRangeScroll(event: WheelEvent) {
    const { deltaY } = event
    const { clientHeight, scrollTop, scrollHeight } = this.$refs
      .detailBox as Element
    if (
      (scrollTop === 0 && deltaY < 0) ||
      (deltaY > 0 && scrollTop + clientHeight === scrollHeight)
    ) {
      return
    }
    // (this.$refs.detailBox as HTMLDivElement).scrollTop = (this.$refs.detailBox as HTMLDivElement).scrollTop / 2; // Todo 奇怪的一点
    ;(this.$refs.detailBox as HTMLDivElement).scrollTop += deltaY

    this.handleScroll(deltaY)
  }

  private onDetailScroll() {
    const { scrollTop } = this.$refs.detailBox as Element
    const deltaY = scrollTop - lastDetailScrollTop
    if (deltaY === 0) {
      return
    }
    this.handleScroll(deltaY)
    lastDetailScrollTop = scrollTop
  }

  private handleScroll(deltaY: number) {
    // Todo 滚动计算时精度问题
    const scrollFlag: 'up' | 'down' = deltaY > 0 ? 'down' : 'up'
    const scrollBarNumber = Math.abs(deltaY) / (this.barHeight + this.barGap)
    const overviewScrollTop =
      (scrollBarNumber / this.rangeTotalRatio) *
      (this.rangeBarHeight + this.rangeBarGap) // 可视区域滚动的bar条数在overview中的高度
    if (deltaY < 0) {
      this.rangeOverviewTop -= overviewScrollTop
    } else {
      this.rangeOverviewTop += overviewScrollTop
    }
    const rangeContentHeight =
      (this.rangeBarHeight + this.rangeBarGap) * this.selfOverviewOptions.length
    if (this.rangeOverviewTop < 0) {
      this.rangeOverviewTop = 0
    }
    if (this.rangeOverviewTop + this.rangeOverviewHeight > rangeContentHeight) {
      this.rangeOverviewTop = rangeContentHeight - this.rangeOverviewHeight
    }
    this.getNewData(scrollFlag)
  }

  /**
   * 向上/向下加载新的数据
   */
  private getNewData(scrollFlag: 'up' | 'down') {
    const { clientHeight, scrollTop, scrollHeight } = this.$refs
      .detailBox as Element
    let loadFlag = false

    if (
      (scrollTop < 50 && scrollFlag === 'up') ||
      (scrollTop + clientHeight + 50 > scrollHeight && scrollFlag === 'down')
    ) {
      loadFlag = true
    }

    if (loadFlag) {
      this.$emit('bar-scroll', {
        deltaY: scrollFlag === 'down' ? 1 : -1,
        currentDataStartIndex: this.selfValue[0].index,
        currentDataEndIndex: this.selfValue.slice(-1)[0].index,
      })
    }
  }

  onDocumentClick() {
    this.$emit('bar-click', { selectedDataArray: [] })
  }

  /**
   * api 更新数据
   */
  public reloadData({ name, data }: any) {
    switch (name) {
      case 'updatePartData': // 更新局部数据
        this.updatePartData(data)
        break
      case 'updateAllData': // 更新全部数据
        this.updateAllData(data)
        break
      case 'updateOverviewData': // 更新右侧overview的数据
        this.updateOverviewData(data)
        break
      default:
        break
    }
  }

  /**
   * 局部更新数据，如滚动加载等，更新detail里面的数据
   */
  private updatePartData(data: any) {
    if (!['up', 'down'].includes(data.dataPosition)) {
      return
    }
    const lastScrollTop = (this.$refs.detailBox as Element).scrollTop
    if (data.dataPosition === 'up') {
      this.selfValue.unshift(...data.detailData) // todo：向上滚动固定在当前位置
      const newScrollTop =
        lastScrollTop + data.detailData.length * (this.barHeight + this.barGap)

      this.$nextTick(() => {
        ;(this.$refs.detailBox as Element).scrollTop = newScrollTop
      })
    } else {
      this.selfValue.push(...data.detailData)
      ;(this.$refs.detailBox as Element).scrollTop = lastScrollTop
    }
  }

  // 更新全部数据
  private updateAllData({ data }: any) {
    console.log(data)
    this.selfValue = [...data]
  }

  /**
   * 更新右侧overview的数据
   */
  private updateOverviewData({ overviewData }: any) {
    this.selfOverviewOptions = overviewData
  }

  public scrollToTop(number = 0) {
    ;(this.$refs.detailBox as Element).scrollTop = number === 0 ? 1 : number
  }

  private onMouseDown(event: any) {
    event.stopPropagation()
    this.isDragOverview = true
    this.overviewOldTop = event.pageY

    document.addEventListener('mouseup', this.onMouseUp)
    ;(this.$refs.container as HTMLElement).addEventListener(
      'mouseup',
      this.onMouseUp
    )
  }

  private onOverviewMouseMove({ pageY }: MouseEvent) {
    if (!this.isDragOverview) {
      return
    }
    const deltaY = pageY - this.overviewOldTop
    const rangeContentHeight =
      (this.rangeBarHeight + this.rangeBarGap) * this.selfOverviewOptions.length
    this.overviewOldTop = pageY

    if (this.rangeOverviewTop + deltaY < 0) {
      this.rangeOverviewTop = 0
    } else if (
      this.rangeOverviewTop + deltaY + this.rangeOverviewHeight >
      rangeContentHeight
    ) {
      // 超出下边距
      this.rangeOverviewTop = rangeContentHeight - this.rangeOverviewHeight
    } else {
      this.rangeOverviewTop += deltaY
    }
  }

  private onMouseUp() {
    this.isDragOverview = false
    const rangeIndex = Math.round(
      this.rangeOverviewTop / (this.rangeBarHeight + this.rangeBarGap)
    )
    const rangeBarData = this.selfOverviewOptions[rangeIndex]
    this.$emit('range-bar-click', {
      selectedRangeIndex: rangeIndex,
      rangeBarData,
      rangeIndex,
    })

    document.removeEventListener('mouseup', this.onMouseUp)
    ;(this.$refs.container as HTMLElement).removeEventListener(
      'mouseup',
      this.onMouseUp
    )
  }

  /**
   * 右键时将事件传递出去
   * 传递条件：右键时的目标是有意义的bar
   */
  private onContextMenu(event: MouseEvent) {
    event.preventDefault()
    if (!this.contextmenuEnable) {
      return
    }
    const { pageX, pageY } = event
    const target = event.target as HTMLElement

    const parentElement =
      this.mode === 'discrete'
        ? this.$refs.detailBox
        : this.$refs.continuousDetailBox
    if (
      this.checkHasParent(target as HTMLElement, parentElement as HTMLElement)
    ) {
      const { top, left } = this.$el.getBoundingClientRect()
      const index = Math.floor(
        (pageY - top - 10) / (this.barHeight + this.barGap)
      )
      const targetData = this.selfValue.slice(index, index + 1)[0]
      // 判断右键点击的目标是否在框选的内容中
      const inSelectionRect =
        this.selectionBarData.findIndex(
          (item) => item.label === targetData.label
        ) > -1

      if (
        !(pageX > left && this.selectionBarData.length > 0 && inSelectionRect)
      ) {
        // 是框选状态或者右键的目标是在框选内容中则不触发目标的点击
        target.click()
      }
      this.$emit('contextmenu', event)
      this.tooltipVisible = false
    }
  }

  private checkHasParent(
    element: HTMLElement,
    targetParent: HTMLElement
  ): boolean {
    let parent = element.parentElement
    while (parent && parent !== targetParent) {
      parent = parent.parentElement
    }
    return !!parent
  }

  private onContainerMousedown({ pageX, pageY }: MouseEvent) {
    if (this.mode === 'discrete') {
      return
    }
    this.isSelection = true
    this.tooltipVisible = false
    this.selectionRect = {
      left: pageX,
      top: pageY,
      width: 0,
      height: 0,
    }
    document.addEventListener('mouseup', this.onContainerMouseup)
  }

  private onContainerMouseup(event: MouseEvent) {
    event.stopPropagation()
    if (this.mode === 'discrete') {
      const classList: string[] = [...(event.target as Element).classList]
      if (
        classList.filter((name) =>
          ['detail-box', 'axis-label', 'continuous', 'discrete'].includes(name)
        ).length > 0
      ) {
        this.$emit('bar-click', { selectedDataArray: [] }) // 清空筛选
      }
      return
    }
    this.calcSelectionBar(event)

    this.isSelection = false
    this.selectionRect = null
    document.removeEventListener('mouseup', this.onContainerMouseup)
  }

  /**
   * 计算框选区域覆盖的bar条数
   */
  private calcSelectionBar(event: MouseEvent) {
    if (!this.selectionRect || this.mode === 'discrete') {
      this.selectionBarData = []
      return
    }
    const top = +this.rectStyle.top.replace('px', '') - 10 // 上内边距有10px
    const height = +this.rectStyle.height.replace('px', '') - 10 // 上内边距有10px
    if (height === -10) {
      // 证明鼠标未进行拖动
      const classList: string[] = [...(event.target as Element).classList]
      if (
        classList.filter((name) =>
          ['detail-box', 'axis-label', 'continuous', 'discrete'].includes(name)
        ).length > 0
      ) {
        this.$emit('bar-click', { selectedDataArray: [] }) // 清空筛选
      }
      return
    }
    const minTop = 0
    let maxTop = 0
    const barHeight = this.barHeight + this.barGap
    let start = -1
    let end = -1

    if (this.mode === 'continuous') {
      maxTop = minTop + this.selfValue.length * barHeight // bar区域最高点
    }
    if (top < maxTop && height > 0) {
      start = top < 0 ? 0 : Math.floor(top / barHeight)
      end = Math.ceil((top + height) / barHeight)
    }

    if (start > -1 && end > -1) {
      this.selectionBarData = this.selfValue.slice(start, end + 1)
      this.$emit('bar-click', { selectedDataArray: this.selectionBarData })
    } else {
      // 拖动空白区域
      this.$emit('bar-click', { selectedDataArray: [] })
    }
  }

  /**
   * 重置状态：框选、框选数据、tooltip等
   */
  public reset() {
    this.isSelection = false
    this.tooltipVisible = false
    this.selectionBarData = []
  }
}
</script>

<style lang="less" scoped>
.column-stat-component {
  height: 100%;
  margin: 0 auto;
  padding-top: 10px;
  position: relative;

  .discrete {
    display: flex;
    height: 100%;
  }

  .detail-outside {
    border: 1px solid transparent;
    height: 20px;
    position: relative;

    &:hover {
      border: 1px solid #ccc;
    }

    .detail-basis,
    .detail-highlight,
    .detail-anomaly,
    .detail-imputation {
      height: 100%;
    }
  }

  .detail-basis {
    background-color: #e9e9e9;
    font-size: 12px;
    position: relative;

    &.active {
      background-color: rgba(105, 115, 255, 0.6);
    }
  }

  .detail-highlight {
    background-color: rgba(105, 115, 255, 0.4);
    left: 0;
    position: absolute;
    top: 0;
  }

  .detail-anomaly {
    background-color: rgba(255, 163, 158, 0.6);
    left: 0;
    position: absolute;
    top: 0;
  }

  .detail-imputation {
    background-color: rgba(255, 163, 158, 0.6);
    left: 0;
    position: absolute;
    top: 0;
  }

  .detail-box {
    flex: 1;
    height: 100%;
    overflow: hidden;
    overflow-y: auto;
    padding-right: 6px;

    &::-webkit-scrollbar {
      display: none;
    }

    .label {
      color: #373b52;
      display: inline-block;
      font-size: 12px;
    }

    .discrete-label {
      left: 4px;
      overflow: hidden;
      padding-left: 9px;
      position: absolute;
      text-overflow: ellipsis;
      top: 0;
      white-space: nowrap;
      width: 97%;
    }

    .is-matching-flag::before {
      border: 3px solid #e6505a;
      border-radius: 3px;
      content: ' ';
      left: 0;
      margin-top: 6px;
      position: absolute;
    }
  }

  .range-box {
    overflow: hidden;
    position: relative;
    width: 17%;

    .range-item {
      margin-left: 3px;
      position: relative;
      width: 75%;
    }

    .range-basis,
    .range-highlight,
    .range-anomaly,
    .range-imputation {
      height: 2px;
      width: 100%;
    }

    .range-highlight {
      background-color: rgba(105, 115, 255, 0.4);
      left: 0;
      position: absolute;
      top: 0;
    }

    .range-anomaly {
      background-color: rgba(255, 163, 158, 0.6);
      left: 0;
      position: absolute;
      top: 0;
    }

    .range-imputation {
      background-color: rgba(255, 163, 158, 0.6);
      left: 0;
      position: absolute;
      top: 0;
    }

    .range-basis {
      background-color: #e9e9e9;
      margin-bottom: 1px;

      &:hover {
        border: 1px solid #ccc;
      }
    }

    .range-overview {
      border: 1px solid #d8d8d8;
      min-height: 5px;
      position: absolute;
      width: 27px;
      z-index: 299;
    }
  }

  .continuous {
    display: flex;
    height: 100%;

    .axis {
      border-right: 1px solid #d8d8d8;
      margin-right: 2px;
      min-height: 11px;
      position: relative;
      width: 6px;

      .axis-tick {
        background-color: #d8d8d8;
        height: 1px;
        left: 0;
        position: absolute;
        width: 100%;
      }
    }

    .axis-label {
      color: #373b52;
      font-size: 12px;
      margin-right: 5px;
      position: relative;
      text-align: right;

      .label {
        left: 0;
        overflow: hidden;
        position: absolute;
        text-overflow: ellipsis;
        white-space: nowrap;
        width: 100%;
      }
    }

    .detail-outside {
      height: 10px;
    }

    .detail-box {
      flex: 1;
      height: 100%;
    }
  }

  .selection-rect {
    background-color: rgba(105, 115, 255, 0.2);
    pointer-events: none;
    position: absolute;
  }
}
</style>
<style>
.calc-label {
  font-family: Helvetica, Tahoma, Arial, STXihei, '华文细黑', 'Microsoft YaHei',
    '微软雅黑', SimSun, '宋体', Heiti, '黑体', sans-serif;
}
</style>
